Go Goroutine
Table of Contents
Section titled “Table of Contents”golang 支持并发和并行, 可以同时跑多个协程
golang 使用 go <function name>()
创建并执行协程
协程本质是一个函数
import ( . "fmt" "time" "sync" ) var wg sync.WaitGroup // 定义 wg 辅助协程计数和执行 func main() { Println(time.Now()) // 执行前打印时间 wg.Add(2) // 标记需要执行两个协程 go func() { // 新建协程, 类似于将函数放后台执行, main 直接接执行下一个函数 cook() wg.Done() // 标记协程执行完毕 } go func() { wash() wg.Done() } wg.Wait() // 等待所有的协程执行完成 Println(time.Now()) // 所有协程执行完后打印时间 } func cook() { time.Sleep(time.Second * 3) // sleep 3s Println("cook by machine use 3s") } func wash() { time.Sleep(time.Second * 2) Println("wash close by machine use 2s") } > 2023-04-01 21:51:20.692681383 +0800 CST m=+0.000016384 > wash close by machine use 2s > cook by machine use 3s > 2023-04-01 21:51:23.693482859 +0800 CST m=+3.000817861
golang 通过新建 2 个协程同时跑 main cook wash 函数, 3 个函数并行执行共花费 3s. main 也是一个协程, main 中若不设置 wg.Wait() 等待其余协程完成, main 执行完成后会关闭所有协程
channel
Section titled “channel”为了确保协程同步, 或协程之间的数据传递, 通道是队列(先进先出)
通道容量为 0, 不能存值, 发送语句和结束语句需要都执行, 否则一方会一直等待另一方导致阻塞
发送语句先执行, 则发送语句阻塞, 等待接收语完后继续执行
接受语句先执行, 则接受语句阻塞, 等待发送语完后继续执行
以上特性常用于协程同步, 无缓冲通道也被称为同步通道
import ( . "fmt" "time" "sync" ) var wg sync.WaitGroup func main() { Printf("start at %v\n", time.Now()) ch := make(chan int, 0) // 初始化通道, 设置缓冲容量为 0 wg.Add(1) go receive(ch) // 创建协程 ch <- 3 // 向通道写入数据, 等待接收语句执行, 数据接收后执行下一句 Printf("send at %v\n", time.Now()) } func receive(c chan int) { time.Sleep(time.Second * 2) rec := <- c // 接收通道数据, 若先执行则等待发送语句执行 Printf("receive %d at %v\n", rec, time.Now())// 发送和接收执行同步 wg.Done() } > start at 2023-04-01 22:52:35.842840438 +0800 CST m=+0.000018823 > receive 3 at 2023-04-01 22:52:37.843993741 +0800 CST m=+2.001172157 > send at 2023-04-01 22:52:37.844102485 +0800 CST m=+2.001280900
通道容量大于 0, 通道允许在容量未满时存值, 容量存满时变成无缓冲通道
import ( . "fmt" "time" "sync" ) var wg sync.WaitGroup func main() { Printf("start at %v\n", time.Now()) ch := make(chan int, 2) // 初始化通道, 设置缓冲容量为 2 wg.Add(2) go receive(ch) // 创建协程接收通道的值 go send(ch) // 创建协程向通道传值 wg.Wait() Printf("over at %v\n", time.Now()) } func send(ch chan int) { ch <- 1 ch <- 2 close(ch) // 向通道传值结束后关闭通道 Printf("send 1,2 at %v\n", time.Now()) wg.Done() } func receive(ch chan int) { time.Sleep(time.Second * 1) rec := []int{} for i := range ch { // 通道关闭后, 使用 range 读取通道值 rec = append(rec, i) } Printf("receive %v at %v\n", rec, time.Now())// 发送和接收执行同步 wg.Done() } > start at 2023-04-02 12:20:34.69317803 +0800 CST m=+0.000016435 > send 1,2 at 2023-04-02 12:20:34.693231007 +0800 CST m=+0.000069413 > receive [1 2] at 2023-04-02 12:20:35.693311209 +0800 CST m=+1.000149614 > over at 2023-04-02 12:20:35.693371227 +0800 CST m=+1.000209634
Go 中一些操作不是并发安全的, 需要锁保证同一时间一个资源只能被一个协程操作
import ( "fmt" "sync" ) func main() { var wg sync.WaitGroup var str string add := func() { for i := 0; i < 500; i++ { str += "a" } wg.Done() } wg.Add(2) go add() go add() wg.Wait() fmt.Printf("str: %d\n", len(str)) } $ go run .\main.go > str: 827
两个协程同时操作 str
, 存在数据竞争, 结果互相覆盖和预期不符
import ( "fmt" "sync" ) func main() { var wg sync.WaitGroup var mu sync.Mutex var str string add := func() { for i := 0; i < 500; i++ { mu.Lock() str += "a" mu.Unlock() } wg.Done() } wg.Add(2) go add() go add() wg.Wait() fmt.Printf("str: %d\n", len(str)) } $ go run .\main.go > str: 1000
使用互斥锁, 保证同一时间资源只被一个协程操作